#include <gtest/gtest.h>
#include <localview/Node.h>
#include <localview/Edge.h>
#include <localview/Network.h>
#include <queue>
#include <Exception.h>
#include <iostream>

using namespace localview;

TEST(Network, getNodeCount) {
	Network* net = new Network();
	Node* node1 = new Node("node1");
	Node* node2 = new Node("node2");

	net->addNode(node1);
	net->addNode(node2);

	ASSERT_EQ(2, net->getNodeCount());

	delete net;
}

TEST(Network, getNodeByName) {
	Network* net = new Network();
	Node* node1 = new Node("node1");
	Node* node2 = new Node("node2");
	Node* node3 = new Node("node3");
	Node* node4 = new Node("node4");

	net->addNode(node1);
	net->addNode(node2);
	net->addNode(node3);
	net->addNode(node4);

	ASSERT_EQ(node3, net->getNodeByName("node3"));
	ASSERT_EQ(node1, net->getNodeByName("node1"));
	ASSERT_EQ(node2, net->getNodeByName("node2"));
	ASSERT_EQ(node3, net->getNodeByName("node3"));
}

TEST(Network, addNode) {

	Network* net = new Network();
	Node* node = new Node("node1");
	int indexInsertedNode;

	indexInsertedNode = net->addNode(node);

	ASSERT_EQ(1, net->getNodeCount());
	ASSERT_EQ(node, net->getNode(0));

	//We only delete the network, since it already
	//delete its nodes;
	delete net;
}

TEST(Network, addNode_addTwice) {
	Network* net = new Network();
	Node* node = new Node("node1");

	int index = net->addNode(node);
	int index2 = net->addNode(node);

	EXPECT_EQ(1, net->getNodeCount());
	EXPECT_EQ(index, index2);

	delete net;
}

TEST(Network, indexOfNode) {

	Network* net = new Network();

	Node* node1 = new Node("node1");
	Node* node2 = new Node("node2");
	Node* node3 = new Node("node2");

	int indexNo1 = -1;
	int indexNo2 = -1;
	int indexNo3 = -1;

	indexNo1 = net->addNode(node1);
	indexNo2 = net->addNode(node2);
	indexNo3 = net->addNode(node3);

	ASSERT_EQ(indexNo1, net->indexOfNode(node1));
	ASSERT_EQ(indexNo1, net->indexOfNode(node1->getId()));

	ASSERT_EQ(indexNo2, net->indexOfNode(node2));
	ASSERT_EQ(indexNo2, net->indexOfNode(node2->getId()));

	ASSERT_EQ(indexNo3, net->indexOfNode(node3));
	ASSERT_EQ(indexNo3, net->indexOfNode(node3->getId()));

	delete net;
}

TEST(Network, getNodeNeighbors) {

	std::vector<Node*> expectNeighbors;

	Network* net = new Network();
	Node* node1 = new Node("node1");
	Node* node2 = new Node("node2");
	Node* node3 = new Node("node3");
	Node* node4 = new Node("node4");

	Edge* edge_1_2 = new Edge(node1, node2, "edge_1_2");
	Edge* edge_1_3 = new Edge(node1, node3, "edge_1_3");
	Edge* edge_1_4 = new Edge(node1, node4, "edge_1_4");

	expectNeighbors.push_back(node2);
	expectNeighbors.push_back(node3);
	expectNeighbors.push_back(node4);

	net->addEdge(edge_1_2);
	net->addEdge(edge_1_3);
	net->addEdge(edge_1_4);

	std::vector<Node*> neighbors;
	std::vector<Node*>::iterator it;

	int index = 0;
	for(it = neighbors.begin(); it != neighbors.end(); it++) {
		EXPECT_EQ(expectNeighbors[index], (*it));
		index++;
	}

	delete net;
}

TEST(Network, addEdge) {

	Network* net = new Network();
	Node* node1 = new Node("node1");
	Node* node2 = new Node("node2");

	Edge* edge1 = new Edge(node1, node2, "node1_node2");

	net->addEdge(edge1);

	ASSERT_EQ(2, net->getNodeCount());

	/*
	 * Check if the nodes was inserted
	 */
	int node1Index = -1;
	int node2Index = -1;

	if(net->getNode(0) == node1) {
		node1Index = 0;
	}else if(net->getNode(0) == node2){
		node2Index = 0;
	}

	if(net->getNode(1) == node2) {
		node2Index = 1;
	}else if(net->getNode(1) == node1){
		node1Index = 1;
	}

	ASSERT_GE(node1Index, 0);
	ASSERT_GE(node2Index, 0);

	/*
	 * Check for the edge count
	 */
	ASSERT_EQ(1, net->getEdgeCount());

	ASSERT_EQ(edge1, net->getEdge(0));

	ASSERT_EQ(edge1, net->getEdgeByName("node1_node2"));

	std::vector<Node*> neighbors;

	neighbors = net->getNodeNeighbors(node1);

	ASSERT_EQ(1, neighbors.size());
	ASSERT_EQ(node2, neighbors[0]);

	delete net;
}

TEST(Network, addEdge_addNode) {

	Network* net = new Network();
	Node* node1 = new Node("node1");
	Node* node2 = new Node("node2");

	Edge* edge1 = new Edge(node1, node2, "node1_node2");

	net->addNode(node1);
	net->addNode(node2);
	net->addEdge(edge1);

	ASSERT_EQ(2, net->getNodeCount());

	std::vector<Node*> neighbors;

	neighbors = net->getNodeNeighbors(node1);

	ASSERT_EQ(1, neighbors.size());
	ASSERT_EQ(node2, neighbors[0]);

	delete net;
}

TEST(Network, initTraversal) {

	Network* net = new Network();
	Node* node1 = new Node("node1");
	Node* node2 = new Node("node2");
	Node* node3 = new Node("node3");
	Node* node4 = new Node("node4");

	net->addNode(node1);
	net->addNode(node2);
	net->addNode(node3);
	net->addNode(node4);

	node1->setTraversalStatus(Node::BLACK);

	net->initTraversal();

	for(unsigned int i = 0; i < net->getNodeCount(); i++) {
		ASSERT_EQ(Node::WHITE, net->getNode(i)->getTraversalStatus());
	}

	delete net;
}

TEST(Network, getNodeOutcomingEdges) {

	Network* net = new Network();

	Node* node1 = new Node("node1");
	Node* node2 = new Node("node2");
	Node* node3 = new Node("node3");
	Node* node4 = new Node("node4");

	Edge* edg_1_2 = new Edge(node1, node2, "edg_1_2");
	Edge* edg_1_3 = new Edge(node1, node3, "edg_1_3");

	Edge* edg_2_4 = new Edge(node2, node4, "edg_2_4");
	Edge* edg_3_4 = new Edge(node3, node4, "edg_3_4");

	Edge* edg_2_3 = new Edge(node2, node3, "edg_2_3");

	net->addEdge(edg_1_2);
	net->addEdge(edg_1_3);
	net->addEdge(edg_2_4);
	net->addEdge(edg_3_4);
	net->addEdge(edg_2_3);

	std::vector<Edge*> expectedEdges;

	expectedEdges.push_back(edg_1_2);
	expectedEdges.push_back(edg_1_3);

	std::vector<Edge*> edges = net->getNodeOutcomingEdges(node1);

	for(int i = 0; i < expectedEdges.size(); i++) {
		ASSERT_EQ(expectedEdges[i], edges[i]);
	}

	delete net;
}

TEST(Network, getNodeOutcomingEdgesCount) {

	Network* net = new Network();

	Node* node1 = new Node("node1");
	Node* node2 = new Node("node2");
	Node* node3 = new Node("node3");
	Node* node4 = new Node("node4");

	Edge* edg_1_2 = new Edge(node1, node2, "edg_1_2");
	Edge* edg_1_3 = new Edge(node1, node3, "edg_1_3");

	Edge* edg_2_4 = new Edge(node2, node4, "edg_2_4");
	Edge* edg_3_4 = new Edge(node3, node4, "edg_3_4");

	Edge* edg_2_3 = new Edge(node2, node3, "edg_2_3");

	net->addEdge(edg_1_2);
	net->addEdge(edg_1_3);
	net->addEdge(edg_2_4);
	net->addEdge(edg_3_4);
	net->addEdge(edg_2_3);

	int count = net->getNodeOutcomingEdgesCount(0);

	ASSERT_EQ(2, count);
}

TEST(Network, getNodeOutcomingEdge) {

	Network* net = new Network();

	Node* node1 = new Node("node1");
	Node* node2 = new Node("node2");
	Node* node3 = new Node("node3");
	Node* node4 = new Node("node4");

	Edge* edg_1_2 = new Edge(node1, node2, "edg_1_2");
	Edge* edg_1_3 = new Edge(node1, node3, "edg_1_3");

	Edge* edg_2_4 = new Edge(node2, node4, "edg_2_4");
	Edge* edg_3_4 = new Edge(node3, node4, "edg_3_4");

	Edge* edg_2_3 = new Edge(node2, node3, "edg_2_3");

	net->addEdge(edg_1_2);
	net->addEdge(edg_1_3);
	net->addEdge(edg_2_4);
	net->addEdge(edg_3_4);
	net->addEdge(edg_2_3);

	ASSERT_EQ(edg_1_2, net->getNodeOutcomingEdge(0, 0));
	ASSERT_EQ(edg_1_3, net->getNodeOutcomingEdge(0, 1));
}

std::queue<Node*> nodesToBeVisited;
std::queue<Edge*> edgesToBeVisited;
void onVisitNode(Node* n);
void onEnqueueNode(Node* n);
void onVisitEdge(Edge* e);

TEST(Network, widthTraversalGraph) {

	while(!nodesToBeVisited.empty()) {
		nodesToBeVisited.pop();
	}
	while(!edgesToBeVisited.empty()) {
		edgesToBeVisited.pop();
	}

	Network* net = new Network();

	Node* node1 = new Node("node1");
	Node* node2 = new Node("node2");
	Node* node3 = new Node("node3");
	Node* node4 = new Node("node4");

	Edge* edg_1_2 = new Edge(node1, node2, "edg_1_2");
	Edge* edg_1_3 = new Edge(node1, node3, "edg_1_3");

	Edge* edg_2_4 = new Edge(node2, node4, "edg_2_4");
	Edge* edg_3_4 = new Edge(node3, node4, "edg_3_4");

	Edge* edg_2_3 = new Edge(node2, node3, "edg_2_3");

	net->addEdge(edg_1_2);
	net->addEdge(edg_1_3);
	net->addEdge(edg_2_4);
	net->addEdge(edg_3_4);
	net->addEdge(edg_2_3);

	nodesToBeVisited.push(node1);
	nodesToBeVisited.push(node2);
	nodesToBeVisited.push(node3);
	nodesToBeVisited.push(node4);

	edgesToBeVisited.push(edg_1_2);
	edgesToBeVisited.push(edg_1_3);
	edgesToBeVisited.push(edg_2_4);
	edgesToBeVisited.push(edg_2_3);
	edgesToBeVisited.push(edg_3_4);

	SCOPED_TRACE("Network::widthTraversalGraph");
	net->widthTraversalGraph(onVisitNode, onEnqueueNode, onVisitEdge);

	ASSERT_EQ(0, nodesToBeVisited.size());
	ASSERT_EQ(0, edgesToBeVisited.size());

	delete net;
}

void onVisitNode(Node* n) {
//	std::cout << n->getId() << " ";
	ASSERT_EQ(n->getTraversalStatus(), Node::BLACK);
	ASSERT_EQ(nodesToBeVisited.front(), n);
	nodesToBeVisited.pop();
}

void onEnqueueNode(Node* n) {
	ASSERT_EQ(n->getTraversalStatus(), Node::GRAY);
}

void onVisitEdge(Edge* e) {
//	std::cout << e->getId() << " ";
	ASSERT_EQ(edgesToBeVisited.front(), e);
	edgesToBeVisited.pop();
}
